﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;

namespace ArtheaEngine
{
    public partial class Character : IWriter, IFormattable, IDisposable
    {
        private Race _race;

        public virtual Room Room { get; set; }

        public Race Race
        {
            get { return _race; }
            set
            {
                _race = value;
                if (value != null)
                    _RaceId = value.Name;
            }
        }

        public string DisplayName
        {
            get
            {
                if (this is NonPlayer)
                {
                    return ((NonPlayer) this).ShortDescr;
                }
                return Name;
            }
        }

        #region IDisposable Members

        public virtual void Dispose()
        {
            if (Room != null)
            {
                Room.Characters.Remove(this);
                Room = null;
            }
            World.Characters.Remove(this);
        }

        #endregion

        #region IWriter Members

        public abstract void WriteLine(string value);
        public abstract void WriteLine();
        public abstract void WriteLine(object obj);
        public abstract void WriteLine(string format, params object[] args);
        public abstract void Write(string value);
        public abstract void Write(string format, params object[] args);
        public abstract void Write(byte[] value);
        public abstract void Write(object obj);
        public abstract void Page(StringBuilder buf);

        #endregion

        partial void OnLoaded()
        {
            _race = World.Races.FindByName(RaceId);
        }

        partial void OnRaceIdChanged()
        {
            _race = World.Races.FindByName(RaceId);
        }

        public bool Equals(Character obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            return obj._CharacterID == _CharacterID && obj._CharType == _CharType;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != typeof (Character)) return false;
            return Equals((Character) obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return (_CharacterID.GetHashCode()*397) ^ _CharType.GetHashCode();
            }
        }

        public bool CanSee(Character who)
        {
            return true;
        }

        public override string ToString()
        {
            return DisplayName;
        }

        public string ToString(string format)
        {
            return ToString(format, CultureInfo.CurrentCulture);
        }

        public void Act(ActType type, string format, params object[] args)
        {
            var temp = new List<object> {this};
            temp.AddRange(args);
            args = temp.ToArray();

            if ((type & ActType.CHAR) != 0)
            {
                WriteLine(string.Format(new ActionFormatter(this), format, args));
            }

            if ((type & ActType.ROOM) != 0)
            {
                foreach (Character ch in Room.Characters)
                {
                    if (ch == this) continue;

                    ch.WriteLine(string.Format(new ActionFormatter(ch), format, args));

                    foreach (CharScript cs in ch.Scripts)
                    {
                        if (cs.Trigger != ScriptTrigger.Act)
                            continue;

                        cs.Process(this);
                    }
                }
            }

            if ((type & ActType.NOTCHAR) != 0)
            {
                foreach (Character ch in Room.Characters)
                {
                    if (ch == this) continue;

                    if (args.Length > 1 && args[1] is Character && args[1] == ch)
                        continue;

                    ch.WriteLine(string.Format(new ActionFormatter(ch), format, args));

                    foreach (CharScript cs in ch.Scripts)
                    {
                        if (cs.Trigger != ScriptTrigger.Act)
                            continue;

                        cs.Process(this);
                    }
                }
            }

            if ((type & ActType.WORLD) != 0)
            {
                foreach (IConnection ch in World.Connections)
                {
                    ch.WriteLine(string.Format(new ActionFormatter(ch.Playing), format, args));
                }
            }
        }

        #region Implementation of IFormattable

        private static readonly string[] HeShe = {"it", "he", "she", "it"};
        private static readonly string[] HimHer = {"it", "him", "her", "it"};
        private static readonly string[] HisHer = {"its", "his", "her", "it"};

        /// <summary>
        ///                     Formats the value of the current instance using the specified format.
        /// </summary>
        /// <returns>
        ///                     A <see cref="T:System.String" /> containing the value of the current instance in the specified format.
        /// </returns>
        /// <param name="format">
        ///                     The <see cref="T:System.String" /> specifying the format to use.
        ///                     -or- 
        ///                 null to use the default format defined for the type of the <see cref="T:System.IFormattable" /> implementation. 
        ///                 </param>
        /// <param name="formatProvider">
        ///                     The <see cref="T:System.IFormatProvider" /> to use to format the value.
        ///                     -or- 
        ///                 null to obtain the numeric format information from the current locale setting of the operating system. 
        ///                 </param><filterpriority>2</filterpriority>
        public string ToString(string format, IFormatProvider formatProvider)
        {
            if (string.IsNullOrEmpty(format) || format == "G")
            {
                if (formatProvider is ActionFormatter)
                {
                    Character viewer = ((ActionFormatter) formatProvider).Viewer;

                    if (!viewer.CanSee(this))
                        return "someone";
                }

                return DisplayName.ToString(formatProvider);
            }

            PropertyInfo prop = GetType().GetProperty(format, BindingFlags.Public | BindingFlags.Instance
                                                              | BindingFlags.IgnoreCase);

            if (prop != null)
            {
                return prop.GetValue(this, null).ToString();
            }

            switch (char.ToUpper(format[0]))
            {
                case 'M':
                    return HimHer[(int) Sex];
                case 'S':
                    return HisHer[(int) Sex];
                case 'E':
                    return HeShe[(int) Sex];
                default:
                    throw new FormatException("unable to locate property");
            }
        }

        #endregion
    }

    public enum Position
    {
        Dead,
        Dying,
        Stunned,
        Sleeping,
        Resting,
        Fighting,
        Standing
    }

    public enum Sex
    {
        Neutral,
        Male,
        Female,
        Hermaphrodite
    }

    public enum Size
    {
        Minuscule,
        Tiny,
        Small,
        Medium,
        Large,
        Huge,
        Giant
    }

    [Flags]
    public enum ActType
    {
        CHAR = (1 << 0),
        ROOM = (1 << 1),
        NOTCHAR = (1 << 2),
        WORLD = (1 << 3)
    }

    public class ActionFormatter : IFormatProvider, ICustomFormatter
    {
        #region Implementation of IFormatProvider

        public ActionFormatter(Character viewer)
        {
            Viewer = viewer;
        }

        public Character Viewer { get; private set; }

        /// <summary>
        ///                     Returns an object that provides formatting services for the specified type.
        /// </summary>
        /// <returns>
        ///                     An instance of the object specified by <paramref name="formatType" />, if the <see cref="T:System.IFormatProvider" /> implementation can supply that type of object; otherwise, null.
        /// </returns>
        /// <param name="formatType">
        ///                     An object that specifies the type of format object to return. 
        ///                 </param><filterpriority>1</filterpriority>
        public object GetFormat(Type formatType)
        {
            return formatType == typeof (ICustomFormatter) ? this : null;
        }

        #endregion

        #region Implementation of ICustomFormatter

        /// <summary>
        ///                     Converts the value of a specified object to an equivalent string representation using specified format and culture-specific formatting information.
        /// </summary>
        /// <returns>
        ///                     The string representation of the value of <paramref name="arg" />, formatted as specified by <paramref name="format" /> and <paramref name="formatProvider" />.
        /// </returns>
        /// <param name="format">
        ///                     A format string containing formatting specifications. 
        ///                 </param>
        /// <param name="arg">
        ///                     An object to format. 
        ///                 </param>
        /// <param name="formatProvider">
        ///                     An <see cref="T:System.IFormatProvider" /> object that supplies format information about the current instance. 
        ///                 </param><filterpriority>2</filterpriority>
        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            var ch = arg as Character;
            if (string.IsNullOrEmpty(format) || ch == null) return arg.ToString();
            return ch.ToString(format, formatProvider);
        }

        #endregion
    }
}